package social.p2p.controller;

import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.swing.SwingWorker;
import net.jxta.peergroup.PeerGroup;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.socket.JxtaServerSocket;
import social.p2p.model.Estado;
import social.p2p.model.Foto;

/**
 *
 * @author cadmilo
 */
public class TransferThread extends Thread {

    public final static int SERVER_PORT = 12457;
    public final static String INIT_ELECTION_OPERATION = "INIT_ELECTION_OPERATION";
    public final static String INIT_ELECTION_OPERATION_OK = "INIT_ELECTION_OPERATION_OK";
    public final static String PING_OPERATION = "PING";
    public final static String PONG_RESP = "PING";
    public final static String GET_OPERATION = "GET";
    public final static String SEPARATOR = ";";
    public final static String COMPARE_OPERATION = "COMPARE";
    public final static String GET_STATE_OPERATION = "GET_STATE";
    public final static String SET_STATE_OPERATION = "SET_STATE";
    public final static String ESTADO = "ESTADO";
    private final ExecutorService es;
    private final JxtaServerSocket serverSocket;
    private boolean running;
    private Socialp2pController controller;

    public TransferThread(Socialp2pController controller, PeerGroup netPeerGroup, PipeAdvertisement pipeAdv) throws IOException {
        es = Executors.newCachedThreadPool();
        serverSocket = new JxtaServerSocket(netPeerGroup, pipeAdv);
        running = true;
        this.controller = controller;
    }

    @Override
    public void run() {
        while (running) {
            try {
                es.execute(new TransferProtocol(serverSocket.accept()));
            } catch (IOException ex) {
                if (!(ex instanceof java.net.SocketTimeoutException)) {
                    Logger.getLogger(TransferThread.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        try {
            serverSocket.close();
        } catch (IOException ex) {
            Logger.getLogger(TransferThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void terminate() {
        running = false;
    }

    private class TransferProtocol implements Runnable {

        private Socket socket;
        OutputStream out;
        InputStream in;
        PrintWriter outWriter = null;
        BufferedReader inReader = null;

        public TransferProtocol(Socket socket) throws IOException {
            this.socket = socket;
            in = socket.getInputStream();
            out = socket.getOutputStream();
            outWriter = new PrintWriter(out, true);
            inReader = new BufferedReader(new InputStreamReader(in));
        }

        @Override
        public void run() {
            EntityManager em = null;
            try {
                String[] request = inReader.readLine().split(";");
                System.out.println("Ejecutando un cliente");
                System.out.println(request);
                // El primer valor es la operacion
                // El segundo valor es el id asociado
                em = Socialp2pController.getEM();
                em.getTransaction().begin();
                if (request[0].startsWith(INIT_ELECTION_OPERATION)) {
                    outWriter.write(INIT_ELECTION_OPERATION_OK);
                    SwingWorker sw = new SwingWorker() {

                        @Override
                        protected Object doInBackground() throws Exception {
                            controller.iniciarProcesoEleccion();
                            return null;
                        }
                    };
                    sw.execute();
                } else if (request[0].startsWith(PING_OPERATION)) {
                    outWriter.write(PONG_RESP);
                } else if (request[0].startsWith(GET_STATE_OPERATION)) {
                    // Enviamos los estados en texto uno por línea
                    System.out.println("Enviando el estado del nodo: ");
                    List<Estado> e = em.createNamedQuery("Estado.selectAll").getResultList();
                    //Si no hay operaciones pendientes no se envía nada (estado idle o vacío)
                    for(Estado est: e) {
                        outWriter.println(ESTADO + SEPARATOR + est.getMetodo() + SEPARATOR + est.getParametros());
                    }
                    outWriter.println("OK");
                    out.flush();
                    em.close();
                } else if (request[0].startsWith(SET_STATE_OPERATION)) {
                    System.out.println("Recibiendo el estado guardado del nodo: ");
                    List<Estado> listaEstados = new ArrayList<Estado>();
                    // Recibimos los estados en texto uno por línea
                    String[] response = inReader.readLine().split(";");
                    while(!response[0].startsWith("OK")) {
                        if (response[0].startsWith(ESTADO)) {
                            //El método viene en el segundo campo, los parámetros vienen en el tercer campo
                            Estado e = new Estado();
                            e.setMetodo(response[1]);
                            e.setParametros(response[2]);
                            listaEstados.add(e);
                            
                        }
                        //Leer la siguiente línea... hasta encontrar el OK
                        response = inReader.readLine().split(";");
                    }
                    controller.ejecutarAcciones(listaEstados);
                } else if (request[0].startsWith(GET_OPERATION)) {
                    // El tercer parametro es el tamaño en el que se desea recuperar la foto
                    Foto f = (Foto) em.createNamedQuery("Foto.getFotoData").setParameter(1, request[1]).getSingleResult();
                    byte[] photo = f.getFoto();
                    out.write(photo);
                    out.flush();
                    em.close();
                } else if (request[0].startsWith(COMPARE_OPERATION)) {
                    // El tercer parametro tiene el tamaño en bytes de la imagen
                    //byte[] foto = new byte[new Integer(request[2])];
                    System.out.println("Recibiendo :" + request[2]);
                    byte[] remotePhoto = new byte[new Integer(request[2])];
                    in.read(remotePhoto);
                    Foto f = (Foto) em.createNamedQuery("Foto.getFotoData").setParameter(1, request[1]).getSingleResult();
                    byte[] localPhoto = f.getFoto();
                    int affinity = Socialp2pController.compare(localPhoto, remotePhoto);
                    outWriter.println(affinity + "");
                }
            } catch (Exception ex) {
                Logger.getLogger(TransferThread.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    em.getTransaction().commit();
                } catch (Exception ex) {
                }
                try {
                    in.close();
                } catch (IOException ex) {
                    Logger.getLogger(TransferThread.class.getName()).log(Level.SEVERE, null, ex);
                }
                try {
                    out.close();
                } catch (IOException ex) {
                    Logger.getLogger(TransferThread.class.getName()).log(Level.SEVERE, null, ex);
                }
                try {
                    socket.close();
                } catch (IOException ex) {
                    Logger.getLogger(TransferThread.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }
}
